package view.GUI.mainView;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import enums.GUIText;
import model.data.tableModel.sale.Product;
import model.data.wrapper.InputWrapper;
import view.Validator.IntegerNumberRangeValidator;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;

/**
 * Direct subclass of <code>VBox</code>. Displays the different textfields for
 * entering the goods to buy.
 * <p>
 * One instance of this class can have multiple rows of <Code>ComboBox</code>
 * and <code>TextField</code> for entering goods that are bought. If something
 * is selected in the <Code>ComboBox</code> a new row is added. The
 * <code>MainView</code> contains several instances of this class in order to
 * represent the different kind of goods that can be bought.
 * <p>
 * Each row is organized as a <code>HBox</code>. This <code>HBox</code> contains
 * several Nodes (atm <Code>ComboBox</code> and <Code>VBox</code>). Every
 * <Code>TextField</code> is added to a <Code>VBox</code> together with its
 * <code>Label</code> which indicates if something is wrong with the current
 * input. The text is validated by a subclass of <Code>ErrorValidator</code>.
 * <p>
 * KindVBox extends VBox
 * <p>
 * _____________________________________
 * <p>
 * |Label Label Label | HBox for Status.DEPOSIT and Status.DRINKS
 * <p>
 * |Combobox TextField TextField |
 * <p>
 * | FailLabel FailLabel |
 * <p>
 * |____________________________________|
 * <p>
 * <p>
 * _________________________
 * <p>
 * s |Label Label | HBox for Status.DEPOSIT and Status.DRINKS
 * <p>
 * |Combobox TextField |
 * <p>
 * | FailLabel |
 * <p>
 * |________________________|
 * <p>
 * TODO improve html layout
 * 
 * @version %I%, %G%
 * @see MainView
 * @see ComboBox
 * @see TextField
 * @see ErrorValidator
 */
public final class KindVBox extends VBox {

	/**
	 * Indicates the kind of good can be bought.
	 * 
	 * @version %I%, %G%
	 * @see MainView
	 * @see KindVBox
	 */
	public enum BoxType {
		DEPOSIT("Pfand"), DRINKS("Getränke"), GROCERIES("Lebensmittel"), OTHER(
				"Sonstiges");

		private final String name;
		private static final Map<String, BoxType> stringToBoxType = new HashMap<>();
		static {
			for (BoxType f : BoxType.values()) {
				stringToBoxType.put(f.toString(), f);
			}
		}

		private BoxType(String name) {
			this.name = name;
		}

		public static BoxType fromString(String s) {

			return BoxType.stringToBoxType.get(s);
		}

		@Override
		public String toString() {

			return name;
		}
	}

	/**
	 * Label of the topic
	 */
	private Label lblTopic;
	/**
	 * HBox containing the topics of the columns
	 */
	private HBox boxLabel;
	/**
	 * List of all HBoxes in this VBox
	 */
	private List<HBox> hBoxes;
	private Label lblKind;
	private Label lblCrate;
	private Label lblFlaschen;
	/**
	 * Separator between top position of borderPane and center Position
	 */
	private Separator sep;
	private BoxType type;
	private MainView mainView;
	private List<Product> productList;

	public KindVBox(BoxType type, MainView mainView,
			Collection<Product> productList) {

		this.getStyleClass().add("vbox");
		this.mainView = mainView;
		this.type = type;
		hBoxes = new ArrayList<>();
		this.productList = new ArrayList<>();
		for (Product p : productList) {
			this.productList.add(p);
		}
		Collections.sort(this.productList);

		createWidgets();
		setLayout();
		addWidgets();
	}

	public void resetInput() {

		productList.clear();
		productList.addAll(mainView.getProducts(false, this.type));
		this.getChildren().remove(3, this.getChildren().size());
		hBoxes.clear();
		this.getChildren().add(createHBox());
	}

	/**
	 * Gets the input that was entered in this KindVBox.
	 * 
	 * @return List of <code>InputWrapper</code> containing the information that
	 *         were entered.
	 * @see InputWrapper
	 */
	public List<InputWrapper> getInput() {

		List<InputWrapper> resList = new ArrayList<>();
		for (HBox h : hBoxes) {
			// Unchecked cast is typesafe because the combobox is always added
			// as first node to the hbox
			@SuppressWarnings("unchecked")
			ComboBox<Product> combo = (ComboBox<Product>) h.getChildren()
					.get(0);
			TextField txtCrate = (TextField) h.getChildren().get(1);
			TextField txtBottle = null;
			if (type.equals(BoxType.DRINKS) || type.equals(BoxType.DEPOSIT)) {
				txtBottle = (TextField) h.getChildren().get(2);
			}
			Product selected = combo.getSelectionModel().selectedItemProperty()
					.get();
			String crateCount = txtCrate.textProperty().get();
			String bottleCount = null;
			if (txtBottle != null) {
				bottleCount = txtBottle.textProperty().get();
			}
			if (selected != null && !"".equals(selected)) {
				InputWrapper input = new InputWrapper(selected, crateCount,
						bottleCount);
				resList.add(input);
			}
		}
		return resList;
	}

	// HELPER METHODS BELOW
	/**
	 * Adds the widgets to its parents.
	 */
	private void addWidgets() {

		boxLabel.getChildren().addAll(lblKind, lblCrate);
		switch (type) {
		case DEPOSIT:
		case DRINKS:
			boxLabel.getChildren().add(lblFlaschen);
			break;
		case GROCERIES:
		case OTHER:
			break;
		default:
			throw new AssertionError("Shouldn't jump to default case.");
		}
		this.getChildren().add(sep);
		this.getChildren().add(lblTopic);
		this.getChildren().add(boxLabel);
		this.getChildren().addAll(createHBox());
	}

	/**
	 * Set the layout of the nodes.
	 */
	private void setLayout() {

		lblTopic.setMinSize(400, 40);
		sep.getStyleClass().add("separator");
		lblCrate.setId("crate-label");
		if (lblFlaschen != null) {
			lblFlaschen.setId("crate-label");
		}

		lblKind.setId("kind-label");

		lblTopic.setId("subtopic");
	}

	/**
	 * Create the widgets.
	 */
	private void createWidgets() {

		boxLabel = new HBox();
		lblKind = new Label(GUIText.KIND.toString());
		switch (type) {
		case DEPOSIT:
		case DRINKS:
			lblCrate = new Label(GUIText.CRATE.toString());
			lblFlaschen = new Label(GUIText.BOTTLES.toString());
			break;
		case GROCERIES:
		case OTHER:
			lblCrate = new Label(GUIText.COUNT.toString());
			break;
		}
		sep = new Separator();
		if (type.equals(BoxType.DEPOSIT)) {
			lblTopic = new Label(GUIText.DEPOSITBACK.toString());
		} else {
			lblTopic = new Label(type.toString());
		}
	}

	/**
	 * Returns a HBox containing all elements needed for input.
	 * 
	 * @return new hbox (combobox, txtfield and eventually another textfield for
	 *         bottles waiting for input
	 */
	private HBox createHBox() {

		final HBox box = new HBox();
		box.getStyleClass().add("hbox");
		final ComboBox<Product> comboBox = new ComboBox<>();
		final TextField txtCrate = new TextField();

		comboBox.getItems().addAll(productList);
//		comboBox.setVisibleRowCount(10);
		addComboBoxListener(comboBox, txtCrate);
		final TextField txtBottle = setupTxtBottle(comboBox, this.type,
				txtCrate);
		comboBox.setPrefSize(350, 25);
		txtCrate.setDisable(true);
		txtCrate.setPrefSize(150, 25);

		// Add listener that updates the paymentGrid if input is changed
		txtCrate.textProperty().addListener(new ChangeListener<String>() {

			@Override
			public void changed(ObservableValue<? extends String> arg0,
					String arg1, String newValue) {

				mainView.getPaymentGrid().showComputedValue();
				if (txtBottle != null) {
					if (newValue != null || !"".equals(newValue)) {
						txtBottle.setDisable(false);
					} else {
						txtBottle.setDisable(true);
					}
				}
			}
		});
		if (txtBottle != null) {
			txtBottle.textProperty().addListener(new ChangeListener<String>() {

				@Override
				public void changed(ObservableValue<? extends String> arg0,
						String arg1, String arg2) {

					mainView.getPaymentGrid().showComputedValue();
				}
			});
		}
		box.getChildren().addAll(comboBox, txtCrate);
		if (txtBottle != null) {
			box.getChildren().add(txtBottle);
		}

		hBoxes.add(box);

		mainView.getValSup().registerValidator(txtCrate,
				IntegerNumberRangeValidator.createMinNumberRangeValidator(0));
		return box;
	}

	/**
	 * Creates the Textfield waiting for input of the bottles
	 * 
	 * @param comboBox
	 * @param type
	 *            Type of the VBox
	 * @return null if type of the VBox is GROCERIES or OTHER | textField for
	 *         DEPOSIT and DRINKS
	 * @throws AssertionError
	 */
	private TextField setupTxtBottle(final ComboBox<Product> comboBox,
			final BoxType type, final TextField txtCrate) throws AssertionError {

		final TextField tempTxtBottle = new TextField();
		comboBox.getSelectionModel().selectedItemProperty()
				.addListener(new ChangeListener<Product>() {

					@Override
					public void changed(
							ObservableValue<? extends Product> arg0,
							Product oldValue, Product newValue) {

						if (newValue != null) {
							int multiplicator;
							switch (type) {
							case DEPOSIT:
								multiplicator = newValue.getMultiplicator();
								break;
							case DRINKS:
								if (newValue.getDeposit() != null) {
									multiplicator = newValue.getDeposit()
											.getMultiplicator();
								} else { // if it is hard alc without deposit
									multiplicator = newValue.getMultiplicator();
									txtCrate.setDisable(true);
								}
								break;
							default:
								multiplicator = 1;
								break;
							}
							int factor = multiplicator;
							if (factor == 1) {
								factor = 0;
							}
							mainView.getValSup().registerValidator(
									tempTxtBottle,
									IntegerNumberRangeValidator
											.createMinNumberRangeValidator(-1
													* factor));
							tempTxtBottle.setDisable(false);
						}
					}
				});
		tempTxtBottle.setDisable(true);
		tempTxtBottle.setPrefSize(150, 25);
		switch (type) {
		case GROCERIES:
		case OTHER:
			return null;
		case DEPOSIT:
		case DRINKS:
			return tempTxtBottle;
		default:
			throw new AssertionError("shouldn't jump to default");
		}
	}

	/**
	 * Adds the listener to the comboBox that creates a new row for input if
	 * something is selected in the comboBox.
	 * 
	 * @param comboBox
	 *            Reference to the box where the listener should be added
	 */
	private void addComboBoxListener(ComboBox<Product> comboBox,
			TextField txtCrate) {

		comboBox.getSelectionModel().selectedItemProperty()
				.addListener(new ChangeListener<Product>() {

					boolean done = false;
					@Override
					public void changed(
							ObservableValue<? extends Product> arg0,
							Product oldValue, Product newValue) {

						txtCrate.setDisable(false);
						if (!done) {
							HBox box = createHBox();
							getChildren().add(box);
						}
						mainView.getPaymentGrid().showComputedValue();

						done = true;
					}
				});

		comboBox.setOnKeyReleased(new EventHandler<KeyEvent>() {
			@Override
			public void handle(KeyEvent event) {
				Product s = jumpTo(event.getText(), comboBox.getValue(),
						comboBox.getItems());
				if (s != null) {
					comboBox.setValue(s);
				}
			}
		});
	}

	private Product jumpTo(String keyPressed, Product currentlySelected,
			List<Product> items) {
		String key = keyPressed.toUpperCase();
		if (key.matches("^[A-Z]$")) {
			// Only act on letters so that navigating with cursor keys does not
			// try to jump somewhere.
			boolean letterFound = false;
			boolean foundCurrent = currentlySelected == null;
			for (Product s : items) {
				if (s.getName().toUpperCase().startsWith(key)) {
					letterFound = true;
					if (foundCurrent) {
						return s;
					}
					foundCurrent = s.equals(currentlySelected);
				}
			}
			if (letterFound) {
				return jumpTo(keyPressed, null, items);
			}
		}
		return null;
	}

	public List<HBox> getHBoxes() {

		return hBoxes;
	}

	public BoxType getType() {

		return this.type;
	}
}
